namespace OpenComposite.Inbox
{
	using System;
	using System.Diagnostics;
	using System.IO;
	using System.Net;
	using System.Reflection;
	using System.ServiceModel;

	using OpenComposite.Base;
	using OpenComposite.HumanActivity;
	using OpenComposite.Inbox.Forms.ServiceReferences;
	using OpenComposite.HumanActivity.Contracts;

	/// <summary>
	/// Forms assembly loader class.
	/// </summary>
	public class FormLoader
	{
		#region Initialization

		/// <summary>
		/// Initializes a new instance of the <see cref="Loader"/> class.
		/// </summary>
		public FormLoader()
		{
			this.FormService = null;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="Loader"/> class.
		/// </summary>
		/// <param name="offline">If offline then loader doesn't use human activity form service.</param>
		public FormLoader(bool offline)
			: this()
		{
			_bOffline = offline;
		}

		#endregion Initialization

		#region Public Members

		#region Properties

		public IHumanActivityFormService FormService { get; set; }

		#endregion Properties

		#region Methods

		/// <summary>
		/// Get assembly by name and load it into AppDomain.
		/// </summary>
		/// <param name="assemblyPath">Path to form assembly directory.</param>
		/// <param name="assemblyBaseName">Assembly base name as string.</param>
		/// <returns>Assembly object.</returns>
		public Assembly GetAssembly(string assemblyPath, string assemblyBaseName)
		{
			Assembly assembly = GetLoadedAssembly(assemblyBaseName);
			if ( assembly != null ) return assembly; // EXIT: Already loaded.

			// Find or download assembly file.
			string assemblyFile = getAssemblyFile(assemblyPath, assemblyBaseName);
			return loadAssemblyFromFile(assemblyFile);
		}

		/// <summary>
		/// Find assembly in list of loaded assemblies.
		/// </summary>
		/// <param name="assemblyBaseName">Base name of assembly.</param>
		/// <returns>Assembly object.</returns>
		public Assembly GetLoadedAssembly(string assemblyBaseName)
		{
			string baseNameToSearch = assemblyBaseName.ToLower();
			foreach ( Assembly assembly in AppDomain.CurrentDomain.GetAssemblies() ) {
				string baseName =
					( assembly != null ? (string)assembly.FullName.Split(',')[0].ToLower() : "" );
				if ( baseName == baseNameToSearch ) return assembly; // EXIT
			}
			return null;
		}

		/// <summary>
		/// Create and return object of specified type from assembly.
		/// </summary>
		/// <param name="assembly">Assembly object.</param>
		/// <param name="assemblyBaseName">Base name of assembly.</param>
		/// <param name="typeName">Type name.</param>
		/// <param name="arguments">Parameters for the constructor, or null.</param>
		/// <returns>Object.</returns>
		public object GetObjectFromAssembly(Assembly assembly, string assemblyBaseName, string typeName, params object[] arguments)
		{
			try {
				if ( typeName.Contains(".") ) {
					return System.Activator.CreateInstance(
						assembly.GetType(typeName),
						arguments); // EXIT
				} else {
					return System.Activator.CreateInstance(
						assembly.GetType(assemblyBaseName + "." + typeName),
						arguments); // EXIT
				}
			} catch ( Exception ex ) {
				throw new BaseException(107005, "Can't create object '{0}' from assembly '{1}'.", ex, typeName, assembly);
			}
		}

		#endregion Methods

		#endregion Public Members

		#region Private Members

		#region Fields

		private bool _bOffline = false;

		#endregion Fields

		#region Methods

		/// <summary>
		/// Load assembly from file.
		/// </summary>
		/// <param name="assemblyFilePath">Path of assembly file.</param>
		/// <returns>Assembly object.</returns>
		private Assembly loadAssemblyFromFile(string assemblyFilePath)
		{
			// Load assembly file into application.
			if ( File.Exists(assemblyFilePath) ) {
				try {
					return Assembly.LoadFile(assemblyFilePath); // EXIT
				} catch ( Exception ex ) {
					throw new BaseException(106001, "Can't load assembly from file '{0}'.", ex, assemblyFilePath);
				}
			} else {
				throw new BaseException(106002, "Assembly file '{0}' not found.", null, assemblyFilePath);
			}
		}

		/// <summary>
		/// Download new versions of the form assembly.
		/// </summary>
		/// <param name="assemblyPath">Path to assembly directory.</param>
		/// <param name="assemblyBaseName">Base name of assembly.</param>
		/// <returns>Filepath of the form assembly.</returns>
		private string getAssemblyFile(string assemblyPath, string assemblyBaseName)
		{
			if ( !Directory.Exists(assemblyPath) ) {
				Directory.CreateDirectory(assemblyPath);
			}
			string assemblyFile = Path.Combine(assemblyPath, assemblyBaseName + ".dll");
			string localVersion = "";
			if ( File.Exists(assemblyFile) ) {
				FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assemblyFile);
				localVersion = fileVersionInfo.ProductVersion;
			}

			// load form from human activity form service
			FormInfo fi = this.FormService.GetFormInfo();
			if ( fi != null && fi.Version != localVersion && fi.URL == "GetForm" ) {
				byte[] bytes = this.FormService.GetForm();
				File.WriteAllBytes(assemblyFile, bytes);
			}
			return assemblyFile;
		}

		#endregion Methods

		#endregion Private Members
	}
}